#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' python 3.7 '''

import tkinter as tk
from tkinter import ttk
import tkinter.filedialog as tf
import tkinter.messagebox as tm
import tkinter.simpledialog as ts
from datetime import date
import os
import logging
import re
import enum
import CXCAL2C_log2 as cc

class _InputDate(ttk.LabelFrame): # 年月日输入组件
    def __init__(self,root,text):
        super().__init__(root,text=text,padding='0 6 0 6')
        self.year = tk.IntVar()  # 020 被识别为八进制16
        self.mon = tk.IntVar()
        self.day = tk.IntVar()
        self.today()        
        ttk.Entry(self,text=self.year).grid()
        ttk.Label(self,text='年').grid(row=0,column=1)        
        ttk.Entry(self,text=self.mon).grid(row=1,column=0)
        ttk.Label(self,text='月').grid(row=1,column=1)        
        ttk.Entry(self,text=self.day).grid(row=2,column=0)
        ttk.Label(self,text='日').grid(row=2,column=1)
        
    def date(self):
        return date(self.year.get(),self.mon.get(),self.day.get())
        
    def today(self):
        now = date.today()
        self.year.set(now.year)
        self.mon.set(now.month)
        self.day.set(now.day)
    
class _MonMery(ttk.LabelFrame): # 本金及与月份相关的小组件
    def __init__(self,root,text,type=1):
        super().__init__(root,text=text,padding='0 6 0 6')
        self.mon = tk.StringVar()
        if type:
            ttk.Combobox(self,text=self.mon,values=sorted(cc.ratesTable.INDEX_L.keys())).grid()
        else:
            ttk.Entry(self,textvariable=self.mon).grid()
        
    def get(self):  return self.mon.get()  
        
class LeftFrame(ttk.Frame): # 左边输入区
    def __init__(self,root):
        super().__init__(root,padding='2 2 0 0')
        self.idate = _InputDate(self,'存入日期：')
        self.money = _MonMery(self,'本金(元)',0)
        self.imon = _MonMery(self,'存期(单位:月 0为活期)')
        self.amon = _MonMery(self,'转存期(单位:月 0为不转存)')        
        self.amon.mon.set('0')
        
        self.odate = _InputDate(self,'取出日期 (滚动中键:当前日期)')
        self.jxr = _MonMery(self,'计息日(每季末月的*日)',0)
        self.jxr.mon.set(cc.lumpCalc._JXR_DAY)
        self.jxr.winfo_children()[0]['state'] = 'disabled'
        
        self.sep = tk.IntVar(value=0)
        self.real = tk.IntVar(value=1)        
        
        self.idate.pack()
        self.money.pack(fill='x')
        self.imon.pack()
        self.amon.pack()
        self.odate.pack()
        self.odate.bind('<MouseWheel>',lambda e:self.odate.today())
        self.jxr.pack(fill='x')  
        ttk.Checkbutton(self,variable=self.real,offvalue=0,onvalue=1,text='活期存期按实际天数').pack(fill='x')       
        ttk.Checkbutton(self,variable=self.sep,offvalue=0,onvalue=1,text='输出步骤').pack(fill='x')
        
    def getOdate(self): return self.odate.date()
    
    def getIrecord(self):
        d = self.idate.date()
        ir = cc.Irecord(d,money=float(self.money.get()),
                          month=int(self.imon.get()),
                          again=int(self.amon.get()))
        if not ir.checkMonth():
            raise ValueError('(转)存期有误...')
        return ir
        
class BottomFrame(ttk.Frame): # 底部框架
    def __init__(self,root):
        super().__init__(root)
        
        tk.Label(self,text='当前费率文件: ',anchor='w',fg='gray').pack(side='left')
        self.file = tk.Label(self,text=os.path.abspath(cc._RATES_FILE),anchor='w',fg='gray')
        self.file.pack(fill='x',side='left')    
        ttk.Button(self,text='退出',command=root.quit).pack(side='right')
        
    def get(self):   return self.file['text']
    
def _ScroText(root,txt): # 滚动条生成与配置
    sc = ttk.Scrollbar(root,command=txt.yview)
    txt['yscrollcommand'] = sc.set
    return sc

def Txtconfigure(master,row,clspan,height): # 配置Text控件及滚动条
    # row：起始行号 clspan: Text控件所占列数 height:控制高
    master.txt = tk.Text(master,height=height)
    master.txt.grid(row=row,column=0,columnspan=clspan,sticky='ns')
    _ScroText(master, master.txt).grid(row=row,column=clspan,sticky='ns')

class calcLX(ttk.Frame):    #零星计算
    def __init__(self,root,app):
        super().__init__(root)
        self.app = app
        
        tk.Button(self,text='开始计算',command=self.calc,relief='groove',font=('',15)).grid(columnspan=2)
        ttk.Label(self,text='计算结果:').grid(row=0,column=0,sticky='sw')        
        Txtconfigure(self, 2, 1,32)

    def calc(self):
        try:
            if self.app.empty_ls():
                raise ValueError('无费率文件或费率表出错...')
            ir = self.app.left.getIrecord()
        except Exception as er:
            self.txt.insert('end','\nERROR:%s'% er)
        else:
            self.app.setHandler(self.txt)
            self.app.baseCalc_sep2(ir)         
        
class showRates(ttk.Frame): # 费率选项卡
    def __init__(self,root,app):
        super().__init__(root)
        self.app = app       
        frm = tk.Frame(self)
        tk.Label(frm,text='提示 :  以月为存期单位 , 0 表示活期',anchor='sw',fg='gray').pack(side='left')
        ttk.Button(frm,text='联网更新利率',command=self.update).pack(side='right')
        ttk.Button(frm,text='更换费率文件',command=self.selecFile).pack(side='right')
        ttk.Button(frm,text='费率备份',command=self.saveAS).pack(side='right')
        ttk.Button(frm,text='显示费率',command=self.show).pack(side='right')
        frm.grid(columnspan=2,sticky='we')        

        col = ['Date',0,3,6,12,24,36,60]
        self.tree = ttk.Treeview(self,height=20,show='headings',columns=col)
        _ScroText(self,self.tree).grid(row=1,column=1,sticky='wns')
        self.tree.grid(row=1,column=0,columnsp=1,sticky='wens')
        
        def sort_date_column():     # 反序
            rowsID = self.tree.get_children()
            for index, itemID in enumerate(reversed(rowsID)): 
                self.tree.move(itemID, '', index)
        # 单击日期列标题可倒序
        self.tree.heading(col[0],command=sort_date_column)
        for id in col:
            self.tree.heading(id, text=id)
            self.tree.column(id,width=68,anchor='e')
        self.tree.column(col[0],width=85)        
        self.tree.bind('<KeyPress-Delete>',lambda e:e.widget.delete(*e.widget.get_children()))
        
    def show(self):
        tb = self.app.calcMethod.table
        if tb.empty():
            tm.showerror('','缺少费率文件...')
        else:
            try:
                items = self.tree.get_children()
                if items:
                    self.tree.delete(*items)
                for irate in tb.getIcon():
                    self.tree.insert('','end',values=str(irate).split())
                for trate in tb.getTcon():
                    self.tree.insert('','end',values=str(trate).split())
            except Exception as er:
                tm.showerror('',er)       
    
    def update(self):
        if not tm.askokcancel('','暂只支持工行,是否继续?'): return
        try:
            cc.lsUpdate(self.app.calcMethod,self.app.bottom.get())
            if self.tree.get_children():
                self.show()
            tm.showinfo(message='已更新')
        except Exception as er:
            tm.showerror('联网更新失败',er)
    
    def selecFile(self):
        path = tf.askopenfilename(filetypes=[('all files','*')])
        if path:
            self.app.calcMethod.table.tableInit(path)
            self.app.bottom.file.config(text=path)
            if self.tree.get_children():
                self.show()
    #    
    def saveAS(self):
        path = tf.asksaveasfilename(initialdir=os.getcwd())
        if not path:return
        try: 
            with open(self.app.bottom.get(),'r') as f:
                with open(path,'w') as fin:
                    fin.write(f.read())
        except Exception as er:
            tm.showerror('',er)
            
class detList(ttk.Frame):  # 计算步骤(详细清单)
    def __init__(self,root):
        super().__init__(root)
        ttk.Button(self,text='保存',command=self.save).grid(sticky='e')
        col = ['date1','date2','本金','利率','税前息','税']
        self.tree = ttk.Treeview(self,height=0,show='headings',columns=col)
        self.tree.grid(row=1,column=0,sticky='wens')
        for id in col:
            self.tree.heading(id, text=id)
            self.tree.column(id,width=68,anchor='center')
        Txtconfigure(self, 2, 1,height=30)

    def save(self):
        v = self.txt.get(1.0,'end')
        if not v.strip(): return
        path = tf.asksaveasfilename(initialdir=os.getcwd(),initialfile=cc._SAVE_FILE)
        if path:
            with open(path,'a') as f:
                f.write(v)
                
class calcLib(ttk.Frame): # 库计算
    def __init__(self,root,app):
        super().__init__(root)
        self.app = app
        
        ttk.Button(self,text='库计算',command=self.calc).grid(column=0,row=0,sticky='w')
        ttk.Button(self,text='库录入',command=self.Irinput).grid(row=0,column=1,sticky='e')
        ttk.Button(self,text='记录删除',command=self.remove).grid(row=0,column=2,sticky='w')
        ttk.Button(self,text='库保存',command=self.save).grid(row=0,column=3,sticky='e')
        ttk.Button(self,text='库文件选择',command=self.selecFile).grid(row=0,column=4,sticky='e')
        ttk.Button(self,text='库显示',command=self.show).grid(row=0,column=5,columnspan=2,sticky='e')
        
        self.file = tk.Label(self,text=os.path.abspath(cc.DEFAULT_IRLIB),fg='gray')
        self.file.grid(row=1,column=0,sticky='sw',columnspan=7)
        
        Txtconfigure(self, 2, 6,30)
        self.irLib = cc.irecordLib(self.file['text']) # 存单库
        
    def save(self):
        dir,filename = os.path.split(self.file['text'])
        path = tf.asksaveasfilename(initialdir=dir,initialfile=filename)
        if path:
            self.irLib.save(path)

    def remove(self):  # 未 保存至 文件
        res = ts.askstring('删除选择',prompt='请根据编号选择(全选请输入A)',initialvalue='A')
        if not res or not res.strip() :  return
        try:
            if res.strip() in 'Aa':
                self.irLib.clear()
                self.txt.insert('end','\n删除成功(结果未保存至文件)')
            else:
                res = re.findall(r'\d+',res)
                for i in map(int,res):
                    ir = self.irLib.find(i)
                    if ir :
                        self.irLib.irs.remove(ir)
                        self.txt.insert('end','\n{}号存单已删除(结果未保存至文件)...'.format(i))
                    else:
                        self.txt.insert('end','\n无{}号存单...'.format(i))            
        except Exception as er:
            self.txt.insert('end','\nERROR:%s'% er)
            
    def show(self):
        res = ts.askstring('库显示选择',prompt='请根据编号选择(全选请输入A)',initialvalue='A')
        if not res or not res.strip(): return
        if res.strip() in 'Aa':
            s = self.irLib.show()
            self.txt.insert('end','\n%s' % (s))
        else:
            res = re.findall(r'\d+',res)
            for i in map(int,res):
                ir = self.irLib.find(i)
                if ir:
                    s = '\n%s' % ir
                else:
                    s ='\n无{}号存单...'.format(i)
                self.txt.insert('end',s)
        
    def selecFile(self):
        path = tf.askopenfilename(filetypes=[('all files','*.*')])
        if path:
            self.file.config(text=path)
            try:
                self.irLib.autoLoad(path)
            except Exception as er:
                self.txt.insert('end','\nERROR:%s' % er)
                
    def Irinput(self):
        try:
            self.irLib.append(self.app.left.getIrecord())
            info = '\n已录入...'
        except Exception as er:
            info = '\nERROR:%s'% er        
        self.txt.insert('end',info)
                
    def calc(self):
        if self.app.empty_ls():
            self.txt.insert('end','\nERROR:无费率文件或费率表出错...')
            return
        res = ts.askstring('选择(非数字符间隔)',prompt='请根据编号选择(全选:A)',initialvalue='A')
        if not res or not res.strip(): return
        
        sum_ = 0.0 
        mtd = self.app.baseCalc_sep2
        self.app.setHandler(self.txt)
        try:
            if res.strip() in 'Aa':
                res = []
                for ir in self.irLib.irs:
                    sum_ += mtd(ir)
                    res.append(str(ir.num))
                if not res:
                    self.txt.insert('end','\nEmpty...')
            else:
                res = re.findall(r'\d+',res) 
                for i in map(int,res[:]):
                    ir = self.irLib.find(i)
                    if ir :
                        sum_ += mtd(ir)
                    else:
                        self.txt.insert('end','\n无{}号存单...'.format(i))
                        res.remove(str(i))                        
            if len(res)>1 and sum_:
                _level = Level.SELF_SEP if self.app.left.sep.get() else Level.SELF
                # logging.getLogger('calc').log()
                logging.getLogger().log(_level,'%s\n%s号本息和汇总(税后):%.2f','-'*60,','.join(res),sum_)
        except Exception as er:
            self.txt.insert('end','\nERROR:%s'% er)

class SepHandler(logging.Handler): # 向TEXT组件输出的处理器
    def __init__(self,frm=None):
        self.frame = frm
        super().__init__()
        
    def emit(self,record):
        msg = self.format(record)
        try:
            self.frame.insert('end','\n%s'% msg)
        except Exception:
            self.handlerError(record)
            
    def setHandler(self,frm):
        self.frame = frm
        
class FilterSep(logging.Filter):  # (级别)过滤器
    def __init__(self,no):
        super().__init__()
        self.levelno = no   
        
    def filter(self,record):
        return (record.levelno <= self.levelno) # 为假被过滤

class Level(enum.IntEnum):
    SEP = 70       # 只输出到清单
    SELF_SEP = 60  # 向两边都输出
    SELF = 50      # 只输出到自身的Text中(不到清单)
            
class calcApp:  # 主配置器
    def __init__(self,root):
        self.calcMethod = cc.lumpCalc(cc.ratesTable(cc._RATES_FILE))
        
        self.left = LeftFrame(root)
        self.left.grid(rowspan=1)
        
        note = ttk.Notebook(root)
        note.grid(row=0,column=1,sticky='wens')
        
        self.sep = detList(note)
        
        fb = SepHandler(self.sep.txt) # 输出到清单处理器:零星、库计算共用(级别为60)
        fb.setLevel(Level.SELF_SEP)   # 此处的级别应为SEP与SELF_SEP间的较小值
        logging.getLogger().addHandler(fb)     # (fb位于根记录器)根记录器级别为30
        
        self.fb = SepHandler()             # 输出到零星或库时的处理器(级别为0)
        self.fb.addFilter(FilterSep(60))   # 过滤器:级别大于60的过滤掉如:步骤
        # logging.getLogger('calc').addHandler(self.fb)    # calc级别为30
        logging.getLogger().addHandler(self.fb)
        
        self.bottom = BottomFrame(root)
        self.bottom.grid(row=1,column=0,columnspan=3,sticky='we')
        
        note.add(calcLX(note,self),text=' 零星计算 ')
        note.add(calcLib(note,self),text=' 库计算 ')
        note.add(self.sep,text=' 计算清单 ')
        note.add(showRates(note,self),text=' 费  率 ')
        
        if self.empty_ls():
            tm.showerror('','加载默认费率文件失败...')
            self.bottom.file['text'] = ''
            
    def empty_ls(self):
        return self.calcMethod.table.empty()
        
    def setHandler(self,txt):
        # hd = logging.getLogger('calc').handlers[0]
        # if txt is hd.frame:    return
        if txt is self.fb.frame:    return
        self.fb.setHandler(txt)   
        # hd.setHandler(txt)   

    def baseCalc_sep2(self,ir): # 
        try:
            # if ir.month is 0:
                # oday = self.left.jxr.mon.get()
                # if oday: cc._JXR_DAY = int(oday)
            real = self.left.real.get()  # 活期是否按实际天数计算存期
            oday = self.left.getOdate()
            sep = self.left.sep.get()    # 步骤 ?
            _level = Level.SELF_SEP if sep else Level.SELF
            lg = logging.getLogger()
            # lg = logging.getLogger('calc')

            res = self.calcMethod.cur_lump_calc_sep(ir,oday,sep,real)
            real = res[0] if sep else res     # 借用来表示本息和
            lg.log(_level,'%s\n%s\n       取出日期: %s\n本息和(税后):%.2f','-'*60,ir,oday,real)
            if sep and res[1]:
                st = ['{}\t\t{}   {:^14.3f}\t{:>5.2f}\t{:^14.3f}\t{:^14.3f}'.format(*i) for i in res[1]]
                lg.log(Level.SEP,
                      '          时间段        \t       本金    \t   利率\t    税前息  \t      税\n%s',
                      '\n'.join(st))    # 只输出到清单
            return real
        except Exception as er:
            lg.log(Level.SELF,'%s\nError: %s','-'*50,er)
            return 0.0   
#
def mainAPP():
    root = tk.Tk()
    root.resizable(0,0)
    root.title('工行个人整存整取(活期)储蓄计算器')    
    calcApp(root)      
    root.mainloop()
    
# if __name__ == '__main__':
mainAPP()
